/* CS32 FINAL PROJECT */
package GameEngine;

import Objects.BasicObject;
import Objects.Player;
import Objects.SpawnPoint;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.List;

/**
 * SERVER/CLIENT PROTOCOL:
 * 
 * Upon Connection, Server waits for Client to verify they are ready to play by listening for a byte.
 *      Client: sends special (byte)
 *      Server: sends ID (short)
 *      Server: sends level background reference (byte)
 *      Server: sends level width (int)
 *      Server: sends level height (int)
 * When all Clients are ready to play the server sends a ready indication to all connected clients
 *      Server: sends game beginning (boolean = true)
 * Once the game begins, the client continuously reads for the state of the game from the server.
 *      Server: sends the initial state of all objects in level to all connected clients
 *          ****** LOOP *******
 *          idnum       : short
 *          graphic     : byte
 *          X direction : float
 *          Y direction : float
 *          X center    : short
 *          Y center    : short
 *          X size      : short
 *          Y Size      : short
 *          exists?     : boolean
 *      Server: sends end of objects (boolean = true)
 *      Client: continuously reads this data above
 * At this point, the server sends at timed intervals the state of the game with the protocol above.
 * Meanwhile, the IncomingClientHandler is continuously reading for actions from the associated client.
 * When an action is received, the information is passed into the calculation for the new state of the level
 *      Client: able to send actions (byte)
 *      Server: continuously reads for actions (bytes)
 * When level calculates a victory condition, the server sends a dummy object with ID = -7. 
 * When client reads this, they set variable that prevents them from sending anymore actions to the server
 * Client then responds acknowledging they know level is over.
 *      Server: sends dummy object (short = -7)
 *      Client: sends acknowledgment (byte = -7)
 * Currently when this happens the server closes and all users are sent back to the main menu.  If work continues on this project
 * we will alter the events following victory condition.
 */
/**
 * This class handles all IO between server and client.
 * 
 * @author crdunwel
 */
public class IncomingClientHandler extends Thread {
    // INSTANCE VARIABLES

    private GameServer server;          // The reference to the GameServer object for each
    public Socket cSock;                // The reference to the Socket of the client
    private DataInputStream in;         // The input stream to the server from the client
    private DataOutputStream out;       // The output stream from the server to the client
    public short UID;                    // The ID number associated with the client
    public boolean readyToPlay;         // The boolean to indicate that this client is ready to play the game

    /**
     * CONSTRUCTOR
     *
     * This object handles the IO between the server and each connected client.  Each client sends information
     * to the server through an associated IncomingClientHandler.
     *
     * @param serv                  The GameServer reference
     * @param clientSock            The client Socket reference
     * @param idnum                 The ID number for the client
     * @throws java.io.IOException
     */
    public IncomingClientHandler(GameServer serv, Socket clientSock) throws IOException {
        server = serv;
        cSock = clientSock;
        in = new DataInputStream(cSock.getInputStream());
        out = new DataOutputStream(cSock.getOutputStream());
        readyToPlay = false;
    }

    /**
     * RUN METHOD
     * 
     * This is the run method for the IncomingClientHandler
     */
    @Override
    public void run() {
        // gets special from client. This is confirmation that client is ready to play.
        byte special = receiveSpecial();
        // adds client to level with initial position equal to the spawn point and with their chosen special.
        // once client has been added, sendClientID() is called.
        setPlayerInLevel(special);
        while (readyToPlay) {
            // loop that continuously reads input from client
            receiveClientActions();
        }
    }

    /**
     * This method is called by the server when a client first connects in order to set the special
     * for that player.
     *
     * @return  byte    the byte representing the special for the player
     */
    public byte receiveSpecial() {
        byte special = -1;
        try {
            special = in.readByte();
        } catch (IOException e1) {
            System.out.println("IncomingClientHandler Error: could not read special from client");
        }
        return special;
    }

    /**
     * This method adds the player to the level and sends them back their object ID and the level ID
     *
     * @param spec  the special power chosen by the player
     */
    public void setPlayerInLevel(byte spec) {
        // creates player object and sets position equal to spawnpoint
        SpawnPoint newSpawnPoint = new SpawnPoint(server.getLevel().getSpawnPoint());
        Player newPlayer = new Player(newSpawnPoint);
        // sets special type for player
        newPlayer.setType(GameConstants.parseByteToSpecial(spec));
        // adds player object to level, returning the asociated ID
        UID = server.getLevel().createObject(newPlayer);
        // refreshes objects in level
        server.getLevel().createObjects();
        // sends ID and level information to client.
        // It is important to call this method before setting readyToPlay = true to preserve protocol
        sendClientID();
        // tells server this client is ready to play
        readyToPlay = true;
    }

    /**
     * This method sends the client their object ID, level ID, and the size of the level
     */
    public void sendClientID() {
        try {
            out.writeShort(UID);
            out.writeByte(server.getLevelBG());
            out.writeInt(server.getLevel().getWidth());
            out.writeInt(server.getLevel().getHeight());
        } catch (IOException e1) {
            System.out.println("IncomingClientHandler Error: could not send client ID");
        }
    }

    /**
     * This method reads input from the client and sends it to the server.
     */
    public void receiveClientActions() {
        try {
            // reads action from client
            byte action = in.readByte();
            // confirmation message from client indicating they recognize the game is over
            if (action == -7) {
                readyToPlay = false;
                stop();
            }
            // sends action from client to server
            server.userAction(GameConstants.parseByteToAction(action), UID);
        } catch (IOException e1) {
            System.out.println("IncomingClietHandler Error: could not read action byte from client");
        }
    }

    // CALLED BY GAMESERVER
    /**********************************************/
    /**
     * This method writes BasicObject information to the outputstream as primitives so the client can update
     * their level.
     *
     * @param bo    A list of BasicObjects for the state of the level
     */
    public synchronized void updateLevel(List<BasicObject> bo) {
        try {
            for (int i = 0; i < bo.size(); i++) {
                BasicObject bobj = bo.get(i);
                short idnum = bobj.getId();
                // an ID of -7 indicates victory condition is met
                if (idnum == -7) {
                    out.writeShort(idnum);
                } else {
                    // GET OBJECT DATA TO PRIMTIVE
                    byte graphic = (byte) bobj.getGraph().ordinal();
                    float dirX = bobj.getDir().getX();
                    float dirY = bobj.getDir().getY();
                    short cenX = (short) bobj.getCenter().getX();
                    short cenY = (short) bobj.getCenter().getY();
                    short sizeX = (short) bobj.getSize().getX();
                    short sizeY = (short) bobj.getSize().getY();
                    boolean exists = bobj.isExists();

                    // WRITE PRIMITIVE OBJECT DATA TO CLIENT
                    out.writeShort(idnum);
                    out.writeByte(graphic);
                    out.writeFloat(dirX);
                    out.writeFloat(dirY);
                    out.writeShort(cenX);
                    out.writeShort(cenY);
                    out.writeShort(sizeX);
                    out.writeShort(sizeY);
                    out.writeBoolean(exists);

                    // CHECK IF END OF OBJECT LIST
                    if (i == bo.size() - 1) {
                        out.writeBoolean(true);
                    } else {
                        out.writeBoolean(false);
                    }
                }
            }
        } catch (IOException e1) {
            System.out.println("IncomingClientHandler Error: could not send client BasicObject data");
            // This will handle when a client disconnects from the server
            server.removeClient(this);
            this.stop();
        }
    }

    /**
     * This method is called by server to send the client the game is ready to bewgin.
     */
    public void sendUserServerReady() {
        try {
            out.writeBoolean(true);
        } catch (IOException ex) {
            System.out.println("IncomingClientHandler Error: could not send client that game is ready to begin");
        }
    }

    // GETTERS AND SETTERS
    /******************************************************************/
    public boolean isReadyToPlay() {
        return readyToPlay;
    }

    public void setReadyToPlay(boolean rdy) {
        readyToPlay = rdy;
    }
    /******************************************************************/
}
